class Compiler {

  constructor(el, vm) {
    this.el = typeof el === 'string' ? document.querySelector(el) : el,
      this.vm = vm

    //编译模板
    if (this.el) {
      //1.将模板的子节点放入内存碎片中, 先不挂入DOM
      const fragment = this.node2fragment(this.el)
      //2.编译模板
      this.compile(fragment)
      //3.将编译好的fragment放回内存中
      this.el.appendChild(fragment)
    }
  }

  /**
   * 将节点的子节点放入fragment中并返回
   * @param {*} node 
   * @returns fragment
   */
  node2fragment(node) {
    const fragment = document.createDocumentFragment()
    const childNodes = node.childNodes
    this.toArray(childNodes).forEach(node => {
      fragment.appendChild(node)
    })
    return fragment
  }

  compile(fragment) {
    const childNodes = fragment.childNodes
    this.toArray(childNodes).forEach(node => {
      //如果是元素, 解析指令
      if (this.isElementNode(node)) {
        this.compileElement(node)
      }

      //如果是文本节点, 解析插值表达式
      if (this.isTextNode(node)) {
        this.compileText(node)
      }

      //如果当前节点还有子节点, 需要递归的解析
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }

  /**
   * 解析HTML标签
   * @param {*} node 
   */
  compileElement(node) {
    //1.获取到当前节点下所有的属性
    const attributes = node.attributes
    this.toArray(attributes).forEach(attr => {
      //2.解析Vue指令(所有以v-开头的属性)
      const attrName = attr.name
      if (this.isDirective(attrName)) {
        const type = attrName.slice(2)
        const expr = attr.value

        //处理v-on指令
        if (this.isEventDirective(type)) {
          CompileUtil['eventHandler'](node, this.vm, type, expr)
        } else {
          CompileUtil[type] && CompileUtil[type](node, this.vm, expr)
        }
      }
    })
  }

  /**
   * 解析文本节点
   * @param {*} node 
   */
  compileText(node) {
    CompileUtil.mustache(node, this.vm)
  }



  toArray(likeArray) {
    return [].slice.call(likeArray)
  }

  /**
   * 判断节点是否是元素节点
   * @param {*} node 
   * @returns 
   */
  isElementNode(node) {
    //nodeType: 1-元素节点 3-文本节点
    return node.nodeType === 1
  }

  /**
   * 判断节点是否是文本节点
   * @param {*} node 
   * @returns 
   */
  isTextNode(node) {
    return node.nodeType === 3
  }

  /**
   * 判断是否是Vue指令 
   * @param {*} name 
   */
  isDirective(attrName) {
    return attrName.startsWith('v-') 
  }

  /**
   * 判断是否是事件指令
   * @param {*} type 
   * @returns 
   */
  isEventDirective(type) {
    return type.split(':')[0] === 'on'
  }
}

const CompileUtil = {
  //处理v-text指令
  text (node, vm, expr) {
    node.textContent = this.getVMValue(vm, expr)
    new Watcher(vm, expr, newValue => {
      node.textContent = newValue
    })
  },

  //处理v-html指令
  html (node, vm, expr) {
    node.innerHTML = this.getVMValue(vm, expr)
    new Watcher(vm, expr, newValue => {
      node.innerHTML = newValue
    })
  },

  //处理v-model指令
  model(node, vm, expr) {

    const that = this

    node.value = this.getVMValue(vm, expr)
    
    //实现 视图 => 数据, 注册input事件
    node.addEventListener('input', function() {
      that.setVMValue(vm, expr, this.value)
    })

    new Watcher(vm, expr, newValue => {
      node.value = newValue
    })
  },

  //处理事件指令
  eventHandler(node, vm, type, expr) {
    //给当前元素注册事件
    const eventType = type.split(':')[1]
    const fn = vm.$methods && vm.$methods[expr]
    if (eventType && fn) {
      node.addEventListener(eventType, fn.bind(vm))
    }
  },

  //获取VM中的数据(兼容复杂对象)
  getVMValue(vm, expr) {
    let data = vm.$data
    expr.split('.').forEach(key => {
      data = data[key]
    })
    return data
  },

  //设置VM中的数据(兼容复杂对象)
  setVMValue(vm, expr, value) {
    let data = vm.$data
    const arr = expr.split('.')
    arr.forEach((key, index) => {
      if (index < arr.length -  1) {
        data = data[key]
      } else {
        data[key] = value
      }
    })
  },

  //文本节点处理的封装
  mustache(node, vm) {
    const text = node.textContent
    const reg = /\{\{(.+)\}\}/
    if (reg.test(text)) {
      const expr = RegExp.$1
      node.textContent = text.replace(reg, CompileUtil.getVMValue(vm, expr))
      new Watcher(vm, expr, newValue => {
        node.textContent = text.replace(reg, newValue)
      })
    }
  }
}
